/**
 * 
 */
package Media.ui.widgets.DeviceView;

import java.io.File;
import java.security.InvalidKeyException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;


import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Shell;

import agg.UIUtils;

import Library.Device;
import Library.DeviceInterface;
import Library.DeviceManager;
import Library.DeviceManager.DeviceAlreadyExistsExcept;
import Media.ui.UIExtendable;




/**
 * @author Hardeep
 *
 */
public class DeviceView extends Canvas {
	/**
	 * Listener for when the DeviceView is disposed.
	 * @author Hardeep
	 */
	private class DeviceViewDisposeListener implements DisposeListener {
		/**
		 * Called when the widget is destroyed...
		 * 
		 * Saves all device data in the DeviceManager and displays the progress
		 * while doing so.
		 */
		public void widgetDisposed(DisposeEvent arg0) {
			if ( !dm.isClosed() ) { 
				// TODO disable all button controls in the parent

				ProgressBarSavingData pbdialog = new ProgressBarSavingData();
				pbdialog.getShell().open();
				ProgressBar pbar = pbdialog.progressBar;

				int i = dm.getNumDevices()+1;
				int j = 0;
				pbar.setMaximum(i);
				pbar.setMinimum(0);

				for ( DeviceInterface d : dm.getDevices() ) {
					try {
						d.shutdown();
					} catch (SQLException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					j++;
					pbar.setSelection(j);
				}

				dm.shutdown();
				pbar.setSelection(j+1);

				pbdialog.closeShell();
			}
		}
	}

	/**
	 * The DeviceManager for this DeviceView
	 */
	private DeviceManager dm;

	/**
	 * A map of Device Names to their Images
	 */
	private ConcurrentHashMap<String, Image> devIcons = 
		new ConcurrentHashMap<String, Image>();

	/**
	 * The order in which to draw the Icons, with each element being the name of
	 * a Device.
	 */
	private ArrayList<String> devOrder = new ArrayList<String>();

	/**
	 * Size of icons to be used, {x/width,y/height}px
	 */
	public int[] icon_size = {64,64};

	/**
	 * X-axis spacing between icons
	 */
	private int x_spacer = 32;

	/**
	 * Y-axis spacing between icons
	 */
	private int y_spacer = 37;
	
	/**
	 * Total space between icons including icon size for x-axis.
	 */
	private int x_icon_space = icon_size[0] + x_spacer;
	
	/**
	 * Total space between icons including icon size for y-axis.
	 */
	private int y_icon_space = icon_size[0] + y_spacer;

	/**
	 * Number of icons to be displayed per row.
	 */
	private int num_per_row = 0;

	/**
	 * Number of rows currently displayed.
	 */
	private int rows = 0;

	/**
	 * The ID of the device selected. The ID correlates to its position in
	 * devOrder, with 0 being the first device.
	 */
	public int selectedDevice = 0;
	
	/**
	 * A reference to this class, for use in event classes.
	 */
	private DeviceView self = this;
	
	/**
	 * A reference to the DeviceView which will be linked with this DeviceView
	 * in sync display mode.
	 */
	private DeviceView dv = null;

	/**
	 * @param parent
	 * @param style
	 */
	public DeviceView(Composite parent, int style) {
		super(parent, style);

		// Get DeviceManager instance
		dm = DeviceManager.INSTANCE;

		// Listens for redraw
		this.addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent e) {
				drawDevices(e);
			}
		});

		// Listens for window close
		this.addDisposeListener(new DeviceViewDisposeListener());
		
		// Listen for mouse click events
		this.addMouseListener(new MouseListener() {
			public void mouseDoubleClick(MouseEvent arg0) {
				// make sure the device gets selected
				mouseDown(arg0);
				
				Device d = getSelectedDevice();
				UIExtendable ed = new EditDevice(self, d);
				ed.setParentShell(self.getShell());
				ed.open();
			}
			public void mouseDown(MouseEvent arg0) {
				int dev_num = getDevNumFromPosition(arg0.x, arg0.y);
				
				if ( dev_num < devOrder.size() )
					setSelectedDevice(dev_num);
			}
			public void mouseUp(MouseEvent arg0) {}
		});

		// check if the DeviceManager already has any devices
		if ( dm.getNumDevices() > 0 ) {
			Image iconImage = null;
			for ( DeviceInterface d : dm.getDevices() ) {
				iconImage = getImageFromDeviceName(d.getName());
				// TODO check to make sure the icon file still exists, if it
				// doesn't then default to something else...
				devIcons.put(d.getName(), iconImage);
				devOrder.add(d.getName());
			}
			Collections.sort(devOrder);
			this.redraw();
		}
		// testing only, set to false to disable
		else if ( true )  {
			System.out.println("Creating test devices...");
			char sep = File.separatorChar;
			String iconsPath = UIUtils.icon_path.replace('/', sep);
			String curDir = ".";
			String iconExt = ".png";
			ArrayList<String> icons = new ArrayList<String>();

			File f = new File(curDir + sep + "src" + iconsPath);
			String[] files = f.list();
			
			for ( String cur_icon : files ) {
				if ( cur_icon.toLowerCase().endsWith(iconExt) ) {
					cur_icon = cur_icon.substring(0, cur_icon.length() - 4);
					icons.add(cur_icon);
				}
			}
			
			System.out.println("# Icons: " + icons.size());
			
			Random r = new Random();
			int rand = 0;
			for (int i = 0; i <= 10; i++) {
				rand = r.nextInt(icons.size());
				Device d = this.addDevice("dev " + i);
				String ic = icons.get(rand);
				System.out.println("Set icon: " + ic + iconExt);
				try {
					d.setProperty("icon", ic + iconExt);
				} catch (SQLException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
			updateImages();
		}
	}
	

	/**
	 * When invoked, the operation turns the view into one which will show all
	 * sync relationships for the selected device in the given devView.
	 * @param devView
	 */
	public void setAsSyncView(DeviceView devView) {
		devOrder.clear();
		// TODO ~~ do this
	}

	/**
	 * Update all the images for each Device. Forces redraw of the widget.
	 */
	public void updateImages() {
		Image iconImage = null;
		for (String dev : devOrder) {
			iconImage = getImageFromDeviceName(dev);
			if ( iconImage != null )
				devIcons.replace(dev, iconImage);
		}
		this.redraw();
	}

	/**
	 * Add a device to the DeviceView. Forces redraw of widget.
	 * @param name the name for the new device
	 */
	public Device addDevice(String name) {
		// first add it to the DeviceManager
		Device dev = null;
		try {
			dev = (Device)dm.newDeviceOfType("generic", name);
		} catch (SQLException e) {
			// TODO Replace with UI warnings
			e.printStackTrace();
		} catch (DeviceAlreadyExistsExcept e) {
			// TODO Replace with UI error dialog
			e.printStackTrace();
			return null;
		}

		Image iconImage = getImageFromDeviceName(name);

		devIcons.put(name, iconImage);
		devOrder.add(name);

		Collections.sort(devOrder);

		this.redraw();

		return dev; 
	}
	
	/**
	 * Set the selected device to the given device number which is the position
	 * ID of the device to select according to the positions in devOrder. Forces
	 * a redraw.
	 * @param devNumber
	 */
	public void setSelectedDevice(int devNumber) {
		selectedDevice = devNumber;
		this.redraw();
	}
	
	/**
	 * Return the Device object for the currently highlighted device.
	 * @return
	 */
	public Device getSelectedDevice() {
		try {
			return (Device) dm.getDevice(devOrder.get(selectedDevice));
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Draw all the devices to the View.
	 * @param e
	 */
	private void drawDevices(PaintEvent e) {
		// TODO make this all neater
		int init_x = 10;
		int init_y = 5;
		
		int x = init_x;
		int y = init_y;

		// text spacing
		int txtSp_x = 0;
		int txtSp_y = icon_size[1] + 2;

		int width = this.getSize().x;
		num_per_row = Math.round(width / x_icon_space);
		rows = 0;
		
		String name = "";
		for (int i = 0; i < devOrder.size(); i++) {
			name = devOrder.get(i);

			if ( devIcons.containsKey(name) ) {
				if ( selectedDevice == i ) {
					// draw background first
					int border = 5;
					int x_pos = x - border;
					int y_pos = y - border;
					
					// draw background highlight
					e.gc.setAlpha(150);
					e.gc.setBackground(new Color(Display.getCurrent(), 0, 0, 0));
					e.gc.fillRoundRectangle(
							x_pos, y_pos, 
							icon_size[0] + border*2, icon_size[1] + border + y_spacer, 
							5, 5);
					
					// text color
					e.gc.setForeground(new Color(Display.getCurrent(), 255, 255, 255));
				}
				else {
					// normal drawing
					e.gc.setBackground(new Color(Display.getCurrent(), 255, 255, 255));
					e.gc.setForeground(new Color(Display.getCurrent(), 0, 0, 0));
				}
				
				e.gc.setAlpha(255);
				e.gc.drawText(formatNameString(name), x+txtSp_x, y+txtSp_y, true);
				e.gc.drawImage(devIcons.get(name),x,y);
			}
			x += x_icon_space;
			if ( x >= (x_icon_space * num_per_row) ) {
				x = init_x;
				y += y_icon_space;
				rows++;
			}
		}
	}
	
	/**
	 * Given an x-y coordinate in the widget, find the device that was clicked
	 * on.
	 * @param x
	 * @param y
	 * @return
	 */
	private int getDevNumFromPosition(int x, int y) {
		int row = y / y_icon_space;
		int dev_in_row = x / x_icon_space;
		int dev_num = (row * num_per_row) + dev_in_row;
		return dev_num;
	}

	private String formatNameString(String name) {
		if ( name.length() > 20 ) {
			name = name.substring(0, 17);
			name += "...";
		}

		String n = "";
		for(int i = 0; i < name.length(); i++) {
			n += name.charAt(i);
			if ( i % 10 == 0 && i > 0 ) n += "\n";
		}

		return n;
	}

	private Image getImageFromDeviceName(String name) {
		ImageData iconData;
		try {
			iconData = new ImageData(
					getClass().getResourceAsStream(UIUtils.icon_path + 
							((Device) dm.getDevice(name)).getProperty("icon")));
		} catch (InvalidKeyException e1) {
			// this should never happen...
			return null;
		}

		Image iconImage = new Image(Display.getCurrent(),iconData);
		return iconImage;
	}
}
